Sense
Containment
Theory
An Alternative Mechanism
For Handling Scope and Sense
The
Sense Alternative Scoping System Author’s manual
Copyright
1999, 2000 by Kevin Forchione.
This manual is the
copyrighted property of Kevin L. Forchione. Permission is granted to distribute
this material by digital and/or physical means on the provision that the
material is distributed in an unmodified form for non-profit purposes. It is
prohibited to redistribute any sub-section from this material separately
without the consent of the author. The author makes no warranty of any kind
with respect to this material, and disclaims all warranties, including any
implied warranties of merchantability or fitness for any particular purpose, or
the continued accuracy of this manual for future versions of the product. I
have made every attempt to make this documentation accurate, but will not be
held responsible for any loss of productivity resulting from errors.
Manual version 3.2 (April 2000)
Written by Kevin L.
Forchione,
c/o Lysseus Dream
Ltd.,
1 Bramford Terrace,
23 Westfield Park
Redland
Bristol UK BS6 6LT
e-mail:
Lysseus@msn.com
If you have any
questions or queries about Scope, you may either write or email me.
Sense.t is Copyright
1999, 2000 by Kevin Forchione for Lysseus Dream Ltd.
Contents
Implicit Limitations of Bubble-up / Filter-down Scoping
2. Sense’s Approach To Accessibility
Case 3: O2 within the same “top level” as
O1
Case 4: O2 NOT within the same “top level”
as O1
Case 5: O2 NOT within a “location”
3. Crossing The Contents Threshold
A Strict Definition Of “Open” and “Closed”
Visible, Audible, Olfactory, Tactile, Reachable and Topic Access
Basic Definitions Of contentsXXXX Attributes
Visualising Action Flow Through The Containment Path
Visualising Crossing The Contents Threshold
Visualising Vantage / Target Flow
Visualising Top-level Location Flow
Four Checks For Action Passage
Interpreting The Pass-To-XXXX Validations
General Meaning of passToObject()
General Meaning of passToLocation()
General Meaning of passToContents()
General Meaning Of passAcrossContents()
Connecting Top-level Locations
5. Describing Objects Directly Addressed
Six Degrees Of Containment Separation
Obtaining Proximity Information
6. Describing Objects Indirectly Addressed
Passing Senses Between Rooms In Room Display
Tips For Building The Room Display
Redefining The ADV.T Container and Surface Classes
The Basic Room Display Listing
Defining An Enterable Surface or Container
The View From An Enterable Surface or Container
The Inside Description Using insideDesc
Defining An Enterable Openable
The View From An Enterable Openable
On Boarding and Unboarding Enterables and Vehicles
9. Library-defined Functions and Methods
Appendix: Modifications To ADV.T & STD.T
room.lookAround(actor, verbosity, sroot)
room.nrmLkAround(actor, verbosity)
deepverb.validDoList(actor, prep, iobj)
deepverb.validDo(actor, obj, seqno)
deepverb.validIoList(actor, obj, seqno)
deepverb.validIo(actor, obj, seqno)
Sense.t is an
extension for HTML TADS’ default parsing/adventuring system contained within
ADV.T. Games that use ADV.T can be modified to run with Sense with minimal
changes, and receive the immediate benefits of the system.
Sense.t provides an
enhancement of the accessibility rules provided by ADV.T. The scope() function
will return a list of all objects that are valid for a vantage for a particular
scope-filter. The path() function will create a scope path between the vantage
and an object.
To fully understand
how the Sense extension works you will need to understand how accessibility
works within the ADV.T library. One of the fundamental ideas of TADS is that
the accessibility rules are not built into the parser, but are part of
ADV.T and adaptable to the requirements of the author. ADV.T provides a basic
set of accessibility rules which are efficient and more than adequate for the
behaviour of its basic set of classes. But for advanced scoping techniques we
must apply an alternate method.
Sense came about
through my studies of the accessibility mechanisms of ADV.T and the
implementation of an accessibility mechanism that allowed for sense
passing and non-ancestor specific scoping. It incorporates and builds
upon the flexible scope list system established in Scope 2.0 and the
rudimentary scope path system of Path 1.0
Accessibility governs
concepts like visibility (what can be
seen) and reachability (what can be
taken). As a process it is the first stage of object resolution. It is helpful,
when thinking about accessibility, to keep the phrase candidate objects in mind, as the result of the application of
accessibility rules should be a list of objects that are valid candidates for
final object resolution. As such we are not concerned at this stage with any of
the mechanisms of final object resolution.
The guiding
philosophy behind Sense.t is
the same that guided Scope.t and Path.t, and has always been one of
least change to the existing library mechanisms. Since accessibility is largely
governed through deepverb's
validXoList() and validXo() methods, one can simply plug the Sense.t module into an
existing game source to replace the ADV.T basic mechanism.
A few other points of
contact remain, however: deepverb’s
cantReach() method, the thing
class verifyRemove() and isVisible() methods. These are the minimal requirements to allow an actor
inside of an openable to behave reasonably with regard to its scope. Further
modifications to allow for room displays are necessary and are documented.
Secondly, efficiency
is an important consideration. Because scoping is an iterative process attempts
have been made to keep the amount of code execution to a minimum. Because the
anticipated behaviours of objects is more complex than the basic classes provided
in ADV.T somewhat more precise determinations of accessibility are involved
which means longer processing time. The added time should, however, appear
negligible on most modern machines.
Finally, modularity
and simplicity of design was aimed for. Sense.t provides a minimal set of
functions that meet the requirements of TADS' accessibility mechanism.
A full copy of the
Sense system, including this manual can be found at Internet location:
ftp.gmd.de in the
/if-archive/programming/tads/examples directory
This is the Sense
Author's Manual. You may use and redistribute this software, free of charge,
with the following restrictions:
1. You must include this file and the copyright notice with all
copies.
2. You may not require or collect a fee for copies of this
software, or any part of this software, that you give to other people.
3. You may not include this software with any other software
for which a fee is collected.
4. You may not modify this software in any way, and each copy you
make and distribute must be a full and complete copy of the software you
originally received.
5. Anyone to whom you give a copy of this software receives all
of the same permissions that you did under this license.
You are permitted to
create and distribute translations of this software into other layout languages
or formats; any such translation shall be subject to the restrictions of this
license as well. You may also make hardcopies of the documentation; hardcopies
are also subject to these restrictions.
These files are
distributed without warranty of any kind, including without limitation any
warranties of merchantability or fitness for a particular purpose. THE READER
ASSUMES FULL RISK AND RESPONSIBILITY FOR USE OF THESE FILES. UNDER NO
CIRCUMSTANCES IS LYSSEUS DREAM, LTD. LIABLE FOR ANY DAMAGES, DIRECT OR
INDIRECT, RESULTING FROM USE OF THIS DOCUMENTATION.
About Version 3.2
This manual was
written in Microsoft Word 2000.
Acknowledgements
Much gratitude is owed
to Michael J. Roberts, author of TADS, whose efforts have enabled others to put
their dreams and ideas into the works of Interactive Fiction. It is hoped that
this manual will provide both instruction and food for the imagination.
I would also like to
than Irene Callaci for her help in testing the demonstration game, and for her
suggestions.
The changes to
sense.t as of release 2.3.0 were the result of a puzzle posted by Graham Nelson
on rec.arts.int-fiction. Changes in the 3.2 release owe a debt of gratitude to
Michael J. Roberts for his suggestions on the iorequires for throwVerb.
System Requirements
Sense requires TADS 2.5.1 or
later.
Required System Files
In addition to
requiring HTML TADS the Sense system is composed of the following files:
Sense.t This file contains the path and
scoping functions and changes to thing, room, nestedroom, and deepverb
necessary to plug Sense into the ADV.T library.
After the parser has parsed the noun phrase it utilises
several parser hooks to help in object resolution. It is at this juncture that
the author can assist the parser in narrowing down the list of objects by
applying certain rules that form the framework of behaviour for the model
world.
We first examine three of these parser-hook stages, and discuss briefly their
impact upon the nature of the model world being constructed.
Access validation is used to construct a list of
"valid" objects - a list of candidates that conform to the behaviours
of the world model rules of accessibility. There are two stages involved in
building this list of valid objects, or list V.
a. validXoList() is the first point of contact the parser
has with regard to "accessibility rules". ADV.T takes the approach of
generally applying visibility-oriented access rules to the building of the
candidates list. This list is then intersected with our original noun phrase
N-list to
produce the V-list.
ADV.T creates this list using the "bubble-up/filter-down" scoping
method of visibleList() - this basically checks whether an object has certain
attributes before allowing iteration down into its contents list. ADV.T tends
to use what I call the "hard-structure" approach toward this
determination.
Hard-structure is based on
class-related distinctions, which remain static throughout the game; as opposed
to "soft-structure" or attribute-related distinctions, which can be
dynamically altered.
For example: the visibleList() function performs the following check to
determine if visibility crosses the contents barrier:
if (not isclass(obj, openable)
or (isclass(obj, openable) and obj.isopen)
or obj.contentsVisible)
A close examination of the use of contentsReachable() shows
that it is defined accordingly:
thing.contentsReachable = (true)
openable.contentsReachable = { return self.isopen }
which performs precisely the definition of:
if (not isclass(obj, openable)
or (isclass(obj, openable) and obj.isopen))
Thus this check in visibleList is functionally equivalent
to:
if (contentsReachable or contentsVisible)
the difference being that an object can modify and override the default value
of contentsReachable, but cannot modify its class affiliation. This means that
an object is always considered visible by visibleList() regardless of its
contentsReachable value.
Hard-structure places the burden of proof within the evaluating function or
method; soft-structure places these determinations with the object being evaluated.
b. validXo: If this method returns true, it means that the
object is valid for the current command - in other words, the object is
accessible to the actor for the purposes of the command.
For this stage ADV.T applies reachability-oriented access rules through the
obj.isReachable() method in building the V list, eliminating those that fail
from the list. Exceptions, such as the inspectVerb apply visibility rules
through the use of obj.isVisible.
Important Things To Note
It's important to note what information is available to the
validXoList() and validXo() methods. In the former, no information is available
about the parsed noun-phrase list (N). We are supplied with the actor,
preposition, and the other object, if they are known at the time; in the later
method we know only the actor, an object from our N-list, and the sequence
number of the object in the N-list. We can obtain preposition and other object
information only if it is saved from the validXoList() method.
In particular, accessibility
rules applied in the validXoList() method have a drastic effect upon the nature
of scope within our world model.
This is because without knowledge of the parsed noun list
(N) we are, in general, left to build the list based on the actor alone.
While this increases parser efficiency, because of the system of containment
used in ADV.T, this usually results in limiting scope to objects within a
single room. In most cases this is perfectly reasonable, but it is a major
defining restriction. Given the information available the
"bubble-up/filter-down" method employed by visibleList() in one form
or another is the only practical approach in creating the validXoList() list.
Expanding the scope by moving room objects into another
container, or by simply doing an object loop, results in increased processing
time spent building a list of candidates, many of which are likely to be
discarded when the N-list and validXoList() list are intersected.
An alternative approach would be to use validXoList() as a filter only for
special verbs that deal with what are likely to be long lists; returning nil
for those verbs that rejectMultidobj, etc. The ask/tell/consult verbs might be
able to use the method as a means of screening out non-topic objects.
If an author chooses to return (nil) from validXoList then s/he must apply both
visibility and reachability rules in the validXo() method -- unless the
isVisible() method is allowed to be used by the parser (see below).
Otherwise the cantReach() mechanism will produce
nonsensical responses, such as "You must open the bottle first" when
"I don't see any foo here." is more appropriate. [The parser message
"I don't see any foo here" produced in step 3. List Matching
indicates that the parser assumes visibility as the minimal level of
accessibility. More on this below.]
Next the parser takes the objects from the V-list and
applies verXo method checking silently to each object for the verb in question,
building an L-list of those that pass verification. These are the
"silent" parser calls for which TADS is famous. They produce a list
of objects for which the action is "logical" (i.e. "can we open
the door?", "can we take the foo?") and are based on the state
of the object, not on its accessibility to the actor.
The parser then compares our V-list with our L-list. We have
the following possibilities:
Things To Note
As mentioned previously, the
isVisible() method is instrumental in determining whether the parser will
display "I don't see any foo here." Or will use the cantReach()
mechanism. Returning (true) from isVisible by default means that cantReach()
must be able to handle the message correctly, making it applicable to more than
just "reachability". This means that the method must have some way of
knowing which accessibility rule the object failed.
The first and most
important limitation is that all accessible objects must share the same
top-level location and lie along an unbroken path in the object-tree of the
vantage. The accessibility mechanisms of ADV.T assume this, and Scope 2.0
enforces this to an even greater degree through its evaluation of the floatingItem class objects.
There may be instances
where you wish to apply a new accessibility rule for a special case verb. In
this instance you need to decide if the bubble up/filter down model of ADV.T is
appropriate. It then becomes a matter of creating a new scope-level definition
and replacing the isScopedef() function. Further than that you may need to make
changes to verification and action methods to round out object behaviour.
But what if you have instances when you wish to include an
object that is not part of the vantage's containment list into its scope?
Sense.t provides a mechanism for this.
As non-ADV.T libraries, such as
WorldClass, have discovered, for the reasons listed above, moving beyond
"visibility" / "reachability" involves altering the approach
taken by ADV.T.
Building a candidates list from
validXoList() involves too little information: you either limit scope to some
arbitrary containment (i.e. "room") or you duplicate the work done in
validXo().
Like WorldClass, the Sense.t library extension
for ADV.T returns (nil) from validXoList(), making all the objects from our
parsed noun list available to validXo().
Because it was the author's desire not to limit access to
"visibility" / "reachability" issues Sense.t also returns
(true) from obj.isVisible, thus bypassing the parser produced "I don't see
any foo here" messages and placing the task of messaging on cantReach().
This places the burden of establishing the accessibility of the world model
within the validXo() method. This method knows the actor and the object in
question. Because ADV.T is not concerned with issues of "disposition"
(i.e. "on", "under", "in", etc) there is no need
for the method to know the preposition of the command (and hence have to deal
with issues of object disposition as WorldClass does.) The validXo() method can
then concern itself with building a path between the actor and the object.
Once you look at it this way, the task is very simple, with only a few
questions to be resolved. Clearly the path between the actor and the object
consists of a nesting of containment through which that action moves either up
or down. [There are five cases that are examined in more detail later in this
document.]
To illustrate, imagine the following:
An actor is
sitting in a chair in his study. Beside him, on a desk is a book of poetry. In
the kitchen a pan of soup is cooking on the stove.
We could construct the path the way NPC
movements are determined, linking one room to another by doors, but this is too
access-specific a means of connecting actor and object. It might be that our
accessibility is not limited to going through the door!
Instead, adopting a containment approach we produce "bubble-up" lists
(clists) for both vantage and target object. This is done the same way that
validXoList determines its high-level location which it passes on to the
filtering process of visibleList().
In our example, the actor is the vantage,
while the soup is the target. Normally the actor is the vantage, because we are
building the path in the direction through which the action will flow.
clist(actor)
= [ actor chair study ]
clist(soup) = [
soup pan stove kitchen ]
We could then simply join our two lists together,
working up the one and down the other to form a logical path between actor and
object, based on containment without having to examine the precise nature of
the containment objects.
Our path for the example cited above from actor to soup would look like:
[ actor chair study kitchen stove pan soup ]
And from actor to book would look like:
[ actor chair study study desk book ]
In many cases we would find that our list held duplicate objects as we moved up
through the actor list and down through the object list. This is especially
true for objects within the same location as our actor.
If we intersect the two lists we discover
whether the objects share a common containment. Since we have
"bubbled-up" for both objects then the first element of the
intersection will be the lowest level of common containment, or lowest-common
containment (lcc). If we have an lcc then we can join the
lists from this point, shortening the path.
The path is then:
[ actor chair study desk book ]
with the lcc being [ study ].
Between the containment lists and the path list we now know:
1.
the vantage object
2.
the target object
3. the logical containment path
4.
the lowest common containment (lcc), if
one exists.
5.
the top-level locations for both vantage
and target
From this we can perform a series of validity checks on
each location in our path for each kind of accessibility we are interested in.
If we have two levels of accessibility we wish to check for, say for example,
visibility and reachability, we can move across our path, checking each object
for visibility. If any of them fail the test then we can return (nil) from
validXo() and handle the message in cantReach(). If every object passes our visibility check then we move across
the path, checking each object for reachability, and repeating the process for
each kind of access we wish to determine.
This is the essence of the Sense approach, which has been designed to give
ADV.T users an alternative model world, which in its default operation should
behave very much like ADV.T itself, but is extensible and adaptable to such
concepts as "sense" passing, containment, and non-containment
specific accessibility.
Before we continue with path validation it may be helpful to visualise the various cases of containment that are possible within a single-contents containment system. There are only five cases we need to examine.
In the simplest case the two objects are the same object.
L1 == L2 == {O1 C1
C2 R1 }
L1 ∩ L2 == L1
L’ == { O1 }
Validation Sequence:
O1 pass-to-object
L1 ==
{ O1 R1 }
L2 ==
{O2 C1 O1 R1 }
L1 ∩ L2 == { O1 R1 }
L’ == { O1 C1 O2 }
Validation Sequence O1 NOT EQUAL LCC
O1 pass-to-object
/ pass-to-location
C1 pass-to-object
/ pass-across-contents / pass-to-location
O2 pass-to-object
Validation Sequence O1 EQUAL LCC
O1 pass-to-object
/ pass-to-contents
C1 pass-to-object
/ pass-across-contents / pass-to-contents
O2 pass-to-object
L1 ==
{ O1 C1 C3 R1 }
L2 ==
{O2 C2 C3 R1 }
L1 ∩ L2 == { C3 R1 }
L’ == { O1 C1 C3 C2
O2 }
Validation Sequence
O1 pass-to-object
/ pass-to-location
C1 pass-to-object
/ pass-across-contents / pass-to-location
C3 pass-to-object
/ pass-to-contents
C2 pass-to-object
/ pass-across-contents / pass-to-contents
O2 pass-to-object
L1 ==
{ O1 C1 C2 R1 }
L2 ==
{O2 C3 C4 R2 }
L1 ∩ L2 == Ǿ
L’ == { O1 C1 C2 R1
R2 C4 C3 O2 }
Validation Sequence
O1 pass-to-object
/ pass-to-location
C1 pass-to-object
/ pass-across-contents / pass-to-location
C2 pass-to-object
/ pass-across-contents / pass-to-location
R1 pass-to-object
/ pass-across-contents / pass-to-location
R2 pass-to-object
/ pass-across-contents / pass-to-contents
C4 pass-to-object
/ pass-across-contents / pass-to-contents
C3 pass-to-object
/ pass-across-contents / pass-to-contents
O2 pass-to-object
L1 ==
{ O1 C1 C2 R1 }
L2 ==
{O2 }
L1 ∩ L2 == Ǿ
L’ == { O1 C1 C2 R1
O2 }
Validation Sequence
O1 pass-to-object
/ pass-to-location
C1 pass-to-object
/ pass-across-contents / pass-to-location
C2 pass-to-object
/ pass-across-contents / pass-to-location
R1 pass-to-object
/ pass-across-contents / pass-to-location
O2 pass-to-object
Before proceeding on to path validation it is probably a
good idea to backtrack slightly, and look at what it means to be a containment
object.
Most object-oriented libraries use a single-location approach to containment. In this approach an object has one location and appear in the contents list for at most only one object at any given time. Even floating items, which have code-determinant locations resolve to one location and one contents list. Even libraries that employ multiple contents list approach resolve objects to one location at any given time.
Given a game object, such as a sack, it is possible to
determine exactly whether an object is inside the sack or not. It is reasonable
to expect to be able to take the object from the sack if the sack is open, but
not if the sack is closed. Likewise we may examine the object if the sack is
transparent, but not otherwise. These are the results of the “open” and
“closed” nature of the sack.
But what exactly does it mean to be “open” or “closed”?
OPEN: When an object is “open” we are
indicating that for a specified action there is access by the object’s location
to the object’s contents; and conversely, access by the object’s contents to
the object’s location.
CLOSED: When an object is “closed” we are
indicating that for a specified action there is no access by the object’s
location to the object’s contents; and conversely, no access by the object’s
contents to the object’s location.
These definitions are very precise and are action-dependent.
Most game objects are assigned an attribute that indicates a status of being
open or closed and is not action-dependent. Later in the chapter we shall
develop terminology to express these determinations.
For example, an apple inside of a closed glass jar is still
visible to the player, even though it is not immediately reachable. So the
precise determination of whether an object is “open” or “closed” is not as
simple as it first appears.
The ability of an action to pass from an object’s location
to its contents, or conversely from its contents to its location is what we
mean by crossing the contents threshold.
But actions, such as <<take>> or
<<examine>> or <<listen to foo>> generally have certain
basic sensory requirements: is the object visible? Is the object reachable?
Rather than providing tests for each possible action defined in the library
(which could be over 100 actions!) we can boil them down to a handful of senses
required to perform them. For example, <<take apple>> and
<<eat apple>> both require that the apple is reachable by the
player in order for the action to succeed.
Rather than use the terms “open” and “closed” we can now
talk about whether an object’s contents are “reachable”, “visible”,
“smellable”, “audible”, etc.
Sense works within the conceptual framework of visibility,
audibility, olfactory, touchability, reachability, and topicality, but these
are defined in terms of the ability of an object to access another object’s
contents using the strict definition of open and closed as interpreted
appropriately for each form of access.
Visibility, audibility, olfactory, and touchability attempt
to simulate the four senses (five including taste, which extends touchability).
These definitions are exclusive of the presence of any medium for conducting or
carrying the sense. For example, visible access is affirmed for an object even
in the absence of light.
Reachability is more abstract in that it pertains to the direct
object’s ability to “touch” the indirect object even when the actor is unable
to do so. For example, an actor shooting an arrow at a target does not require
touch access to the target, but the arrow does. The arrow must be able to
“touch” the target before the action of striking the target can be completed.
Topicality is the ability of an actor to access an object
that is otherwise non-sensible. These objects usually function as the indirect
objects of conversation.
Once we have built our scope path L’ we need to determine if
the action can travel along the path. Scope defined 5 attributes each object
inherits from thing class to help make this
determination. These act as two-way streets determining if the object allow
passage across the contents threshold.
contentsVisible: this method determines if
the contents of an object are visible from the outside. But this is basically a
two-way street. If the contents are visible from the outside, then the outside
is visible from the contents.
contentsAudible: this method determines if
the contents of an object are audible across the contents threshold. By default
openable objects return true for contentsAudible, regardless of whether they
are open or closed.
contentsOlfactory: this method determines if
the contents of an object are smellable across the contents threshold. By
default openable objects return true for contentsOlfactory, regardless of whether
they are open or closed.
contentsTouchable: this
method determines if the contents of an object are touchable from the outside.
This, too, is a two-way street. If the contents are touchable from the outside,
then the outside is touchable from the contents.
contentsReachable: this
method determines if the contents of an object are reachable from the outside.
This, too, is a two-way street. If the contents are reachable from the outside,
then the outside is reachable from the contents.
The table below shows the return values for the three
classes in ADV.T / Sense.t that directly define contentsXXXX attributes. Each
object is composed of the 5 attributes, the attributes can return either true,
nil, or self.isopen, which gives us 125 possible class combinations.
Fortunately, for all practical purposes we can reduce this number to a mere
handful.
Class |
contentsVisible |
contentsAudible |
contentsOlfactory |
contentsTouchable |
contentsReachable |
thing |
True |
True |
True |
True |
True |
openable |
Self.isopen |
Self.isopen |
Self.isopen |
Self.isopen |
Self.isopen |
containment |
if (Self.isTransparent ||Self.ispermeable) True; else
Self.isopen; |
if (Self.ispermeable) True; else Self.isopen; |
if (Self.ispermeable) True; else Self.isopen; |
Self.isopen; |
if (Self.ispermeable True; else Self.isopen; |
transparentItem |
True |
Self.isopen |
Self.isopen |
Self.isopen |
Self.isopen |
permeable |
True |
True |
True |
Self.isopen |
True |
thing
A thing has no contents, so in a sense we
cannot talk about its behaviour when crossing the contents threshold. But for
the sake of completeness thing class objects return true for
each of the 5 contentsXXXX attributes. These values are inherited by all objects
that extend thing class.
openable
Access for each of the 5 contents threshold attributes is
determined by whether the object isopen == true or nil. Thus the contents of an
openable are completely inaccessible when the openable is closed. Bank safes,
shoe boxes, mail boxes, and other openable containers make up this class.
containment
The containment class is polymorphic in
that it depends upon attributes to determine the characteristics of its
containment. Thus the containment class object can behave
like an openable, transparentItem, permeable, room or nestedroom.
transparentItem
The transparentItem class comprises objects
whose contents are still visible even when the container is closed. These can
be glass bottles, plastic boxes, etc. A transparentItem does not allow sound or
smell to travel across its contents threshold when closed, nor can objects be
projected into it when closed.
permeable
The permeable class behaves very much like a
cage. When the permeable is closed objects inside can be
seen, heard, and smelled, and objects outside projected into the permeable, but the
contents of the permeable cannot otherwise be touched. However, when open the permeable allows
complete access, just as the openable and transparentItem classes
do.
Sense defines access_rules for use in cases where contents
threshold evaluation is necessary. These functions receive only 1 parameter:
the object to be evaluated, and return true if the object passes the test;
otherwise nil. The access_qXXXX rules are used in the display process of
extended sense passing.
See Appendix for details.
Checking whether or not we can cross the contents threshold
is not enough to determine whether the vantage has access to the target object.
The contentsXXXX attributes act as two-way streets they basically apply the
rule:
If I can
access you, you can access me.
But there may be occasions where we don’t want this reflexivity
to hold true. Without some kind of check the path O1 => O2 is “open” contingent only upon
the action and the “open / closed” nature of the attributes of the objects
along the path.
For instance, because top-level locations are defined as “open”
(for technical reasons), it would be possible to reach into other rooms and
take things, but not be able to take things from closed containers that are in
those other rooms.
It’s easiest to visualise the passage of action along the
containment path as a sort of chain stretched between the vantage to the target
object or as a pipe through which the action flows like water. Thus, with an
action such as <<listen to foo>> the action flows through the actor
to the actor’s location, and then along the containment path to the foo’s
location and finally to the foo.
The
following diagram represents action “flowing” from O1 through O2
to O3 across the contents threshold of O2. Since the
contentsXXXX attributes are reflexive the action can flow equally in the
opposite direction.
If we create a path list to represent the flow of the action we must first decide which object is the vantage and which is the target. In other words, a path is defined to evaluate the movement of an action in one direction only. Suppose we examine the path of action from O1 through O2 to O3.
[ O1 O2
O3 ]
Since the action is originating with the O1 object
we must determine whether it can pass to this object’s contents. We are not
concerned with whether the action can cross the object’s contents threshold. To
make this clear, suppose the actor is reaching across a table for a steel box.
We don’t care if the action can cross the contents threshold of the target
(steel box), and indeed we would have a problem if the box were closed.
Likewise, it could be conceived that a “closed” object might
initiate the action, and so we would not want to concern ourselves with
whether the action could cross the contents threshold of the vantage (in most
cases the actor).
We can envisage the passage of the action across the
containment path from O1 to O3 thus:
O1
pass-to-contents O2 cross-the-contents-threshold O2 pass-to-contents
O3
Moving across the containment path in the opposite direction
from O3 to O1 would be represented thus:
O3
pass-to-location O2 cross-the-contents-threshold O2
pass-to-location O1
Along with each of these checks, we will include a check to
determine if the object itself will allow the action to pass. While this check
may seem unnecessary, it can be very useful, especially for the target object.
From the
diagram it is obvious that the flow of action for the vantage may involve a
pass-to-location or a pass-to-contents, depending upon whether the target
object is within the vantage or outside of it. An easy way to determine this is
by checking the target containment list. If the vantage is within the target
containment list then action flows along pass-to-contents; otherwise it flows
along pass-to-location.
The vantage is a special case, in that we check the
pass-to-object validity, but we are not concerned with whether the vantage is
“open” or “closed”.
It makes little sense to check for pass-to-location,
pass-to-contents, or even pass-across-contents-threshold for the target object.
Instead, for the author to exercise any particular control over the access
rules for this object they must use pass-to-object alone.
In this case the disposition of the lowest common
containment object (whether it is open or closed, transparent or not, etc) is
irrelevant to the flow of the action, since the action is not crossing the
contents threshold of the lowest-common containment object.
Because we
have not crossed the contents threshold the meaning of pass-to-location and
pass-to-contents become ambiguous. It is cleaner, therefore to perform a
simpler check upon the lowest common containment: pass-to-object. While much of
the time this will simply return true, there could be occasions when an author
wishes to limit access within the lowest common containment object. This could
be the case when the player is “suspended within an orange cloud” or within an
unlit room.
But once we’ve accepted this check it also begins to make
sense to check the action’s eligibility to pass-to-contents.
The flow of action from the vantage to the target may appear to pose a couple of problems at first. The flow of action takes exactly the same path as it does when an lowest-common containment exists. But what happens at the top-level locations, where we have a mathematical discontinuity point?
At this point WorldClass resolves the problem by providing a
TOP object into which all other objects are moved. It also uses a NIL object,
which is also within TOP, to hold objects that have been moved out of the
object-tree. Sense takes a simpler approach. It skips over the point of
discontinuity in a manner that is analogous to the determination of the
derivative in calculus. Can the action pass-to-location at C1; and
specifically is the pass-to-location indicated as C2? Can the action
pass-to-object at C2? Finally, can the action pass-to-contents at C2?
Thus we treat the evaluation the same as the case where a lowest-common
containment exists.
Thus each object in our path must pass at least one of the four action checks:
a.
action can pass to object
b.
action can pass to location
c.
action can pass to contents
d.
action can pass across the object’s contents
threshold
The Sense.t passToLocation(), passToContents() and
passToObject() and passAcrossContents() methods are simple checks to determine
whether or not the object will allow scope to pass through it. A path is validated moving the access_filter
across the path list from vantage to target (i.e. from element[1] to
element[n], where n is the length of the list). Although the rules may appear
complicated, they really are not. A pseudo-code representation boils it down to
the following checks:
For each object in path
{
pass-to-object
if (o == target) continue
if (o != vantage
&& o != lcc)
pass-across-contents
if (find(clist(target),
o))
pass-to-contents
else
pass-to-location
}
Each thing-class object inherits pass-to methods that
determine the accessibility of the action as it passes along the containment
path. For most objects these methods simply return true, indicating that access
is unimpeded. Because these methods are used to define the nature of the failed
access it is important to understand what Sense expects to be happening when
one of these methods fails.
This method is new to Sense 3.0, and is checked for every
object along the path. It represents a failure of access to the object itself
and can best be thought of in terms of redirecting the action to this object.
For example, suppose we have a ball inside of a closed glass bottle that is
sitting upon a shelf. The action <<take ball>> would return “You
must first open the bottle” if we validated the path because it would fail at
bottle.passAcrossContents().
But suppose we want to make the shelf so high that the actor
cannot reach it. We could code this in TADS using the xobjCheck() and xobjGen()
methods the same as for distantItem objects, but there’s a
problem. Because action doesn’t flow along the containment path under ADV.T we
have no way of controlling actions directed to objects that are in the distantItem.contents list. We
are left with two choices: either coding the restriction in actor.roomAction()
or in the target object itself (we could code the validation in verbAction()
but this is even worse for O-O design.). In the first solution, problems arise,
however, when the action is coming from a remote location (as in a composite
room); in the second solution, we are forced to put the same code into every
object which might conceivably end up on or in the distantItem.
But because Sense validates the action along the containment
path we can use passToObject() to block a specified action at a certain point
along that path. This means that action will fail for the object and every
object within its contents. Thus, in our example, we might allow
<<throw>> and <<inspect>> access through
shelf.passToObject, while failing other actions, if we want to create a high
shelf that objects can be throw onto or inspected, but cannot be reclaimed.
Suppose the Alice is sitting upon the mantelpiece in Through
The Looking Glass. What can she reach? We can use this method to prevent
actions from passing to an object’s location. We might want to limit access to
visibility, for instance, or for getting down onto the chair.
This method is also used to control accessibility from
top-level locations. Ordinarily rooms are defined as top-level locations, and
accessibility does not pass beyond the room. But by using this method we can
pass the action along to other locations, thus skipping over the “discontinuity
point” of the path list.
Analogous to passToObject, this method can be used when you
want to be able to access an object, but not its contents. For example a very
high wardrobe might be touchable, but you may be unable to reach anything that
is on top of it.
Sense 3.0 has redefined the meaning and use of the
passAcrossContents() method. In earlier versions this method was used only in
conjunction with the lowest common containment (see chart next chapter), which
now uses the passToObject() method.
This method represents the pass-across-contents-threshold as
defined above. Its default definition is thus:
return runRoutine(self, filter);
which is the same as (return self.contentsXXXX). This was done to make the passToXXXX methods the central point of accessibility control. Previous versions of Sense.t required special modifications of the contentsXXXX code for making access_filter exceptions. This is no longer necessary, and all exceptions can now be handled in the passToXXXX methods.
Passing action across top-level locations poses a special
problem. The pass-to-location check provides the most logical point at which to
validate access across top-level locations, but the coding can become
cumbersome, especially if we need to connect several locations.
In the room class four special xxxxInto methods are defined
to provide a bridge between top-level locations:
These methods must return one of three values: a top-level
location object, a list of top-level location objects, or nil.
The room.passToLocation() calls the retrieveLocs() method to
determine if the location indicated by the xxxxInto method is the top-level location
of the target object. If this is the case then retrieveLocs() returns true;
otherwise retrieveLocs() returns nil.
This concept deals with the idea that an object’s
description is relative to the positions of the viewer and the object itself
and what kinds of senses are involved.
For example (in general), an object on a chair with the
actor is in closer proximity to the actor than the same object would be within
the room. While this may not be true in every case, knowing the relative
proximity of the actor and target object can help in the formulation of a
reasonable description of the object.
There are the following six degrees of proximity:
Proximity |
Description |
Reflexive |
The actor and the object are one and the same. This is
the case where the action is self-reflexive. You can’t get closer than this. |
Possessive |
The next closest proximity. The actor has the object in
its possession |
Contained |
Lowest Common Containment is the target object. The
actor is within the target object, such as a box. |
Immediate: |
Lowest Common Containment, not the Room. The actor and the object share the same containment,
such as a chair or a box |
Local |
Lowest Common Containment is the Room The actor and object share the same top-level location. |
Remote |
No Lowest Common Containment The actor and object are in different rooms that are
linked together |
Sense.t provides a function to help determine the proximity of vantage and target: proximity(vantage, target); This function takes the vantage and target as arguments and returns a list consisting of the following elements:
[
PRX_XXX [ access_filter list] ]
Where the first element is a PRX_XXXX flag is one of the following values:
PRX_REFLEXIVE:
The vantage is also the target
PRX_POSSESSIVE:
The vantage possesses the target
PRX_CONTAIND:
The vantage is contained within the
target
PRX_IMMEDIATE:
Both vantage and target share a
lowest-common containment that is not a top-level room (i.e. a chair or bed,
etc.)
PRX_LOCAL:
Both vantage and target share a
lowest-common containment that is a top-level room (i.e. the Foyer)
PRX_REMOTE:
The vantage and target do not
have a lowest common containment (i.e. They’re in separate rooms.)
The second element is a list of all the access_filters valid for the path between vantage and target object (i.e. &access_visible, &access_audible, &access_olfactory, &access_reachable).
If an access_filter is passed then the method returns the PRX_XXXX flag for the proximity between the object and the vantage if the path is valid for that access_filter; otherwise it returns nil.
|
|
sdesc |
Short description for the object when it is in the same
room as the actor – default definition |
sdesc_reflexive |
Short description for the object when it is the same as
the actor. |
sdesc_possessive |
Short description for the object when it is the possession
of the actor. |
sdesc_contained |
Short description of the object when the actor is
inside of it. |
sdesc_immediate |
Short description of the object when the actor and
object share a common containment that is not a top-level room: i.e. a chair
or a bed. |
sdesc_remote |
Short description of the object that is not in the same
top-level location (i.e. room) as the actor. |
This same schema is used for adesc, thedesc, ldesc, smelldesc, and listendesc. All of the special extensions default to their root description, as follows:
sdesc_xxxx defaults to sdesc
adesc_xxxx defaults to adesc
thedesc_xxxx defaults to thedesc
ldesc_xxxx defaults to ldesc
listendesc_xxxx defaults to listendesc
smelldesc_xxxxx defaults to smelldesc
Description routers exist to help determine which extended description method to apply and should be used in message generations in place of the usual description attribute and can be imbedded (placed in <<>>) just like any other description attribute. For example, instead of calling ldesc you should call ldescR, which will then determine which of the ldesc_xxxxx attributes apply. The messages are built up recursively by the router, so that thedescR calls sdescR and so forth. For example:
“\^%You% see nothing unusual about <<self.thedescR>>. “
will ensure that an extended sense message attribute is used to describe the object. If none is found to match the proximity then the router will move up and down the containment levels until it finds a message to display.
The following table displays the default method, the router, and the list of extended description methods corresponding to each proximity flag.
Default |
Router |
PRX_REFLEXIVE |
PRX_POSSESSIVE |
PRX_CONTAINED |
PRX_IMMEDIATE |
PRX_LOCAL |
PRX_REMOTE |
sdesc |
sdescR |
sdesc_reflexive |
sdesc_possessive |
sdesc_contained |
sdesc_immediate |
sdesc |
sdesc_remote |
adesc |
adescR |
adesc_reflexive |
adesc_possessive |
adesc_contained |
adesc_immediate |
adesc |
adesc_remote |
thedesc |
thedesR |
thedesc_reflexive |
thedesc_possessive |
thedesc_contained |
thedesc_immediate |
thedesc |
thedesc_remote |
pluraldesc |
pluraldescR |
pluraldesc_reflexive |
pluraldesc_possessive |
pluraldesc_contained |
pluraldesc_immediate |
pluraldesc |
pluraldesc_remote |
ldesc |
ldescR |
ldesc_reflexive |
ldesc_possessive |
ldesc_contained |
ldesc_immediate |
ldesc |
ldesc_remote |
listendesc |
listendescR |
listendesc_reflexive |
listendesc_possessive |
listendesc_contained |
listendesc_immediate |
listendesc |
listendesc_remote |
smelldesc |
smelldescR |
smelldesc_reflexive |
smelldesc_possessive |
smelldesc_contained |
smelldesc_immediate |
smelldesc |
smelldesc_remote |
heardesc |
heardescR |
heardesc_reflexive |
heardesc_possessive |
heardesc_contained |
heardesc_immediate |
heardesc |
heardesc_remote |
touchdesc |
touchdescR |
touchdesc_reflexive |
touchdesc_possessive |
touchdesc_contained |
touchdesc_immediate |
touchdesc |
touchdesc_remote |
tastedesc |
tastedescR |
tastedesc_reflexive |
tastedesc_possessive |
tastdesc_contained |
tastedesc_immediate |
tastedesc |
tastedesc_remote |
thrudesc |
thrudescR |
thrudesc_reflexive |
thrudesc_possessive |
thrudesc_contained |
thrudesc_immediate |
thrudesc |
thrudesc_remote |
readdesc |
readdescR |
readdesc_reflexive |
readdesc_possessive |
readdesc_contained |
readdesc_immediate |
readdesc |
readdesc_remote |
heredesc |
heredescR |
heredesc_reflexive |
heredesc_possessive |
heredesc_contained |
heredesc_immediate |
heredesc |
heredesc_remote |
actorDesc |
actorDescR |
actorDesc_reflexive |
actorDesc_possessive |
actorDesc_contained |
actorDesc_immediate |
actorDesc |
actorDesc_remote |
The router will first determine the proximity and check to see
if the object has the corresponding extended description attribute. If the
object doesn’t then it checks to see if it has the next highest level of
proximity, moving from reflexive to remote. If we reach the end of the list the
router will begin working down the list from the point where proximity was
determined. Since all sense default messages are defined at the local level all
description messages will eventually default at the local value.
Building and
validating a scope path between a vantage and a target object, as we have seen,
is a fairly straightforward affair. In that respect <<take the
ball>> is handled roughly the same way whether we have a lowest common
containment or not.
But building the room
display, especially in the case where senses are passed from one top-level
location to another, is slightly more complicated. The procedure can be
outlined as follows:
First building a path
from actor to other room locations:
Actor çè other room
location
In Sense 3.0 the
seeInto, hearInto, smellInto, and reachInto methods are used to form the bridge
from the top-level location of the vantage to the top-level locations to which
we are passing the action. The path between the vantage and this other room are
validated in order to save processing time. If the passToLocation() method of
the top-level location of the actor returns nil then we continue with the next
top-level location in the xxxxInto list. If there are no more locations then
the process is finished.
Once we have a valid
path between the actor and an other room top-level location we must create a
list of valid objects within the room. We do this using a bubble scope routine
that makes its determinations based on a contents threshold check and a check
for “quiet” containers. A check for “quiet” containers at this stage is
necessary because we are doing a room display, and not determining
simple accessibility.
The next step is to
validate the path between the actor and each object.
Actor çè object
This is done so that the
other pass-to checks can be done on the target object. It may seem like a lot
of work, but it logically guarantees that the display produced by an action
such as <<smell obj>> and the same object presented by the global
action <<smell>> have the same behaviours.
Sense generates room
displays in a flexible manner. Each room has a nrmXxAround() and xtndXxAround()
method for customising the room description when the room is being sense as
part of the actor’s containment list, and when it is not.
In addition the
isListed and isqxxxx attributes used to control room listing display in ADV.T
are paralleled in Sense.t
Every object
inheriting from thing class is provided with the following listing attributes:
·
isListed: used the same as in ADV.T. Determines
whether the object appears in room listings for visible scope.
·
isListedAudible: determines whether the object appears in
room listings for audible scope.
·
isListedOlfactory: determines whether the object appears in
room listing for olfactory scope.
For example, if
an object defines a smelldesc it will not appear in a nrmSmAround() or
xtndSmAround() listing unless it returns true for isListedOlfactory. On the
other hand, the object will appear in a the nrmSmAround() or xtndSmAround()
listing even if it does not define a smelldesc directly, if it’s
isListedOlfactory attribute returns true.
Sense.t defines 3
attributes that control whether an object displays its contents in a room listing
or is “quiet”.
·
IsqVisible: The object does not display its contents for
visibility in room displays.
·
IsqAudible: The object does not display its contents for
audibility in room displays.
·
IsqOlfactory: The object does not display its contents for
olfaction in room displays.
Both the qsurface
and qcontainer class definitions return true by default for all three isqXxxx
attributes.
A perusal of Sense reveals that building the room display is a complicated affair. By coordinating the various scoping and listing attributes, as well as modifying the listing mechanisms themselves an author has a lot of flexibility – and getting all the various aspects of room display to work in harmony takes some practise.
Some tips:
· If you want an object to display in room display listings, it must have its isListedXxxx attribute return true. You can toggle this attribute to your advantage.
· Actors inherit from qcontainer, and therefore hide their contents. However, Sense.t movableActor defaults to returning true for isqAudible and isqOlfactory. This means that an NPC’s possessions will automatically appear in room descriptions if they have the isListedXxxx attribute. This also can be toggled to your advantage.
· Containers, by default do not prevent sense passing when closed. This can be toggled in the individual object or as a class.
By defining USE_CONTAINMENT at the top of their game source
an author can tell the compiler to implement the containment class
which will then be used in place of the standard ADV.T container and surface
classes. If you do not wish to implement this definition of containment simply
do not define USE_CONTAINMENT.
The containment class implements a
POLYMORPHIC object class that consolidates the behaviours from the following
ADV.T classes:
This class allows the author to easily
define containment objects possessing different
behaviours, all from one base class. The class is polymorphic, in that the
containment behaviours of an object can be transformed into that of another by
simply changing the value of a few attributes.
To illustrate the flexibility of this class let’s develop
and extend the sample game provided by the TADS workbench.
#define USE_HTML_STATUS
#define USE_HTML_PROMPT
#define USE_CONTAINMENT
First we indicate that the game is to use
the html status and prompt as well as the common sense approach for sense.t.
Next we include the TADS adventure and standard libraries.
#include <adv.t>
#include <std.t>
The sense.t class provides an alternate scoping mechanism to
that of ADV.T, as well as implementing sense-passing facilities. The
containment.t file defines the containment class, as well as implementing some
refinements to boarding and unboarding.
#include <sense.t>
To make things a little simpler, we #include the doors.t
module, which defines a door class.
#include <doors.t>
We want to compile the game using the C-style operators, so
we indicate this with:
#pragma C+
And we turn the html interpreting on:
replace commonInit: function
{
/*
display the special code sequence to turn on HTML recognition */
"\H+";
}
Now that all the preliminaries are finished we are ready to
code game objects. The most natural place to begin is with the startroom.
startroom: containment
lightsOn
= true
sdesc =
"Entryway"
ldesc =
{
"This large, formal entryway is slightly intimidating:
the
walls are lined with somber portraits of gray-haired
men from
decades past; a medieval suit of armor";
/* if
the axe is in the armor, list it specially */
if
(axe.isIn(suitOfArmor))
", posed with battle axe at the ready,";
" towers over a single straight-backed wooden chair.
The
front door leads back outside to the south.
A
hallway leads north.";
}
north =
hallway
south =
frontDoor
out =
frontDoor
;
The only difference between this definition and the one
provided by that of the TADS workbench sample is the inheritance from
containment class and the lightsOn attribute, which indicates that this is a
lit room. If we wished to make this a “darkroom”, one that is dependent upon a
lightsource we would leave this attribute as nil.
By default all containment class objects behave as “darkrooms” requiring a lightsource for lighting. To make a containment class object behave as though it were self-lit set the lightsOn attribute to true.
When a player does a <<look>> or enters a room a
description of the room and its contents is generated. The basic containment class
room display listing is composed of the following parts:
a.
The room short description (sdesc)
b.
The room long description (ldesc)
c.
Actor descriptions
d.
A sentence-style listing of the room contents (listcont())
e.
A sentence-style listing of objects on or in any room
contents containers (listcontcont())
f.
A firstseen description (first-time only)
Entryway
This
large, formal entryway is slightly intimidating: the walls are lined with
somber portraits of gray-haired men from decades past; a medieval suit of
armor, posed with battle axe at the ready, towers over a single straight-backed
wooden chair. The front door leads back outside to the south. A hallway leads
north.
Joe is
here, looking for lerts.
You see
a chess board, a lidless ceramic jar, and a bag here. Sitting on the chess
board is a red queen. The lidless ceramic jar seems to contain a candle
(providing light).
Our first game object is the suit of armor. Although the
armor has an isqcontainer attribute it is defined by the
workbench sample code as a fixeditem, and not as a surface or container. We do
not need to make any changes to its definition. Neither do we need to modify
the definition of the axe or the portraits.
suitOfArmor: fixeditem
location
= startroom
sdesc =
"suit of armor"
noun =
'suit' 'armor'
adjective
= 'medieval'
ldesc =
{
"It's a suit of plate-mail armor that looks suitable
for a
very tall knight. ";
/* if
I'm holding the axe, so note */
if
(axe.isIn(self))
"The armor is posed with a huge battle-axe held
at the ready. ";
}
isqcontainer = true
;
axe: item
sdesc =
"battle axe"
location
= suitOfArmor
noun =
'axe' 'blood' 'blade' 'edge'
adjective
= 'steel' 'battle' 'dried'
ldesc =
"It's a large steel battle axe. A
little bit of
dried blood on the edge of the blade makes the authenticity
of the equipment quite credible. "
;
portraits: fixeditem
location
= startroom
noun =
'portraits' 'pictures' 'men'
adjective
= 'somber' 'gray-haired'
sdesc =
"portraits"
isThem =
true
adesc =
"a portrait"
;
Lastly, let’s add an actor, someone we
can order around a little.
joe: Actor
location
= startroom
noun =
'joe'
sdesc =
"Joe"
adesc =
"Joe"
thedesc =
"Joe"
isHim =
true
actorDesc
= "Joe is here, looking for lerts."
actorAction(v, d, p, i) = {}
;
Now we can code the containment class
objects. We begin by defining those that behave like surfaces.
chessBoard: containment
issurface
= true
location
= startroom
noun =
'board' 'chessboard'
adjective
= 'chess' 'checker' 'chequer'
sdesc =
"chess board"
;
The issurface attribute
indicates whether this object should behave like a surface. Set this attribute
to true if you want the object to behave like a surface; otherwise leave it nil
(default).
Things can be put on this
object and taken off of it. An object is said to be “on”, rather than “in” this
object.
If enterable, an actor may
board and unboard this object using
Board obj, get on obj, get
onto obj, sit on obj, lie on obj
Get off obj, get off of
obj, stand <up>
This attribute “inherits”
from other surface-related attributes. Specifically, an object whose isqsurface attribute
is set to true will return (issurface == true)
Like containment objects in both ADV.T and Inform, the
containment.t containment class should not define an object
with both surface and container attributes. This is because the class uses one
contents list and content-objects do not keep track of their disposition
relative to their location. Instead this is left up to the containment object.
Let’s add a red queen for our chessboard. The queen is
simply an ordinary item:
redQueen: item
location
= chessBoard
noun =
'queen'
adjective
= 'red'
isHer =
true
sdesc =
"red queen"
ldesc =
"She's a fierce little chess piece."
;
If we take the queen, we get the following message.
>take queen
Taken.
And if we put the queen back on the
chessboard we get this message.
>put the red queen on the chessboard
Done.
We can also examine the chessboard.
>x chessboard
On the chess board you see a red queen.
If we try to get on the chessboard we get
this message.
>get on chessboard
That's not something you can enter.
As defined above, the chessboard is not enterable. This
means that actors cannot board or enter the object, although items and
movableActors can be placed in containers or on surfaces. By default
containment class objects that have surface and container behaviours are not
enterable.
In the Entryway we have a ceramic jar
with a lit candle inside.
jar: containment
iscontainer = true
location
= startroom
noun =
'jar'
adjective
= 'lidless' 'ceramic'
sdesc =
"lidless ceramic jar"
;
By defining an iscontainer attribute in a
containment class object we are indicating that the object should behave like a
container.
The iscontainer attribute
indicates whether this object should behave like a container. Set this attribute
to true if you want the object to behave like a container; otherwise leave it
nil (default).
Things can be put in this
object and taken out of it. An object is said to be “in”, rather than “on” this
object.
If enterable, an actor may
board and unboard this object using
Board obj, get in obj, get
into obj, enter obj (explicit)
Get out obj, get out of
obj, exit obj (explicit)
The enter and exit commands
require the explicit use of the direct object to differentiate them from room direction
commands.
This attribute “inherits”
from other container-related attributes. Specifically, an object whose isqcontainer,
isopenable, islockable, or istransparent attributes are set to true
will return (iscontainer == true)
We also code a lightsource, to be used in
locations that require light.
candle: lightsource
location
= jar
noun =
'candle'
sdesc =
"candle"
islit =
true
;
Openable objects are a special form of container
useful for creating bottles, bags, boxes, and other objects that you might want
to open or close. The Entryway has one non-transparent openable, a bag:
bag: containment
isopenable = true
location
= startroom
noun =
'bag'
sdesc = "bag"
insideDesc = "%You're% inside a close confining space."
ldesc = {
"It looks like a sleeping bag. ";
pass
ldesc;
}
;
The isopenable attribute
indicates whether this object should behave like an openable. Set this attribute
to true if you want the object to behave like an openable; otherwise leave it
nil (default).
This object behaves like a
container that can be opened and closed.
If enterable, it behaves
like a container.
This attribute “inherits”
from other container-related attributes. Specifically, an object whose islockable, or istransparent
attributes are set to true will return (isopenable ==
true)
All containment class objects have their
isopen attribute set to true by default. Accessibility rules demand that an
object be open in order to gain access to its contents. The definition of
non-openable containment class objects should always have isopen set to true if
access to their contents is desired.
When the openable is open we can put
things in it and take things out of it. When it is closed we cannot access its
contents.
>put red queen in the bag
Done.
>close the bag
Closed.
>take the red queen
You can't see any red queen here.
>open the bag
Opening the bag reveals a red queen.
>x the red queen
She's a fierce little chess piece.
>take it
Taken.
The last object to be defined in the Entryway
is the wooden chair. This is the first containment class object we’ve defined
that permits an actor to board it.
chair: fixeditem, containment
issurface
= true
isenterable = true
location
= startroom
sdesc =
"wooden chair"
noun =
'chair'
adjective
= 'straight-backed' 'wooden'
;
The chair is a fixeditem – it cannot be
taken. Otherwise it behaves similarly to the chessboard, except that by
overriding the isenterable attribute and setting it to true we have made it an
enterable surface.
The isenterable attribute
is only applicable in conjunction with the issurface or iscontainer attributes.
Only containment class objects with isenterable = true attributes will allow an
actor to board them.
By default a containment
class object is not enterable.
We can board the chair in several
different ways: sit on chair, get on chair, board chair.
>sit on chair
Okay, you're now sitting on the wooden chair.
The view from the chair is taken from a
perspective as though the actor were half on and half off the chair,
incorporating the main description from the room. The short description now
indicates that the actor is “on” the chair.
Entryway, on the wooden chair
This
large, formal entryway is slightly intimidating: the walls are lined with
somber portraits of gray-haired men from decades past; a medieval suit of
armor, posed with battle axe at the ready, towers over a single straight-backed
wooden chair. The front door leads back outside to the south. A hallway leads
north.
Joe is
here, looking for lerts.
You see
a chess board, a lidless ceramic jar, and a bag here. Sitting on the chess
board is a red queen. The lidless ceramic jar seems to contain a candle
(providing light).
Containment class objects may define an
insideDesc attribute that describes the view from inside the object. This is
analogous to a room ldesc, and should not mention the objects contents
unless they are not listed and are of special reference.
When an actor does a <<look>>
from the vantage of an enterable containment class object the main room
description excludes the actor’s location. The description of the actor’s
location, the insideDesc is displayed after the display of the room’s contents
by listcont() and listcontcont(). Following the enterable’s insideDesc we list
the enterable’s contents via listcont() and listcontcont().
Suppose we put the chessboard on the
chair beside us.
>put chessboard on chair
Done.
>l
Entryway, on the wooden chair
This
large, formal entryway is slightly intimidating: the walls are lined with
somber portraits of gray-haired men from decades past; a medieval suit of
armor, posed with battle axe at the ready, towers over a single straight-backed
wooden chair. The front door leads back outside to the south. A hallway leads
north.
Joe is
here, looking for lerts.
You see
a lidless ceramic jar and a bag here. The lidless ceramic jar seems to contain
a candle (providing light).
On the
wooden chair you can see a chess board. Sitting on the chess board is a red
queen.
Since our chair does not define an
insideDesc nothing is displayed for this. But the last line is the contents
listing for the chair using listcont() and listcontcont().
Next we define the two other rooms of the
house, the hallway and the kitchen. These are simple containment class rooms,
and are defined similarly to the Entryway.
hallway: containment
lightsOn
= true
sdesc =
"Hallway"
ldesc =
"This broad, dimly-lit corridor runs north and south. "
south =
startroom
north =
kitchen
;
kitchen: containment
lightsOn = true
sdesc = "Kitchen"
ldesc = "This is a surprisingly cramped kitchen, equipped
with
antiques: the stove is a huge black iron contraption,
and there doesn't even seem to be a refrigerator. A
hallway lies to the south. "
south = hallway
;
Our Kitchen contains a few very
interesting containment class objects. The first is a stove, hiding a loaf of
bread.
stove: fixeditem, containment
isopenable = true
location
= kitchen
noun =
'stove' 'oven' 'contraption' 'door'
adjective
= 'huge' 'black' 'iron' 'stove'
sdesc =
"stove"
ldesc =
{
"It's a huge black iron cube, with a front door that swings
open
sideways. The door is currently
<<
self.isopen ? "open" : "closed">>. ";
/*
list my contents if there's anything inside */
if
(self.isopen && itemcnt(self.contents) != 0)
"Inside the stove you can see <<listcont(self)>>.
";
}
/* it
starts off closed */
isopen =
nil
;
Like the chair, we’ve made this a fixeditem, so it can’t be
taken. We’ve also defined its isopen attribute to be nil, so its initial state
is closed, and we don’t see the bread until we open the stove.
loaf: fooditem
location
= stove
sdesc =
"loaf of bread"
ldesc =
"It's a fresh loaf with a golden-brown crust. "
noun =
'loaf' 'bread' 'crust'
adjective
= 'fresh' 'golden-brown' 'brown'
doEat(actor) =
{
"You tear off a piece and eat it; it's delicious. You tear off
a
little more, then a little more, and before long the whole loaf
is
gone. ";
/*
make the bread vanish by moving it to "nil" */
self.moveInto(nil);
}
;
Our kitchen also has a table, a simple enterable surface, to
demonstrate the use of the insideDesc attribute:
table: containment
issurface
= true
isenterable
= true
location
= kitchen
noun =
'table'
sdesc =
"table"
insideDesc = "The surface of the table is small and cramped."
;
We’ll add an apple, to keep the doctor away!
apple: fooditem
location
= table
noun =
'apple'
sdesc =
"apple"
adesc =
"an apple"
;
The view from the table would look something like this, once
we’ve added the glass bottle definition:
>get on table
Okay, you're now on the table.
>l
Kitchen, on the table
This is a
surprisingly cramped kitchen, equipped with antiques: the stove is a huge black
iron contraption, and there doesn't even seem to be a refrigerator. A hallway
lies to the south.
The
surface of the table is small and cramped.
On the
table you can see a glass bottle and an apple.
The first four lines are the kitchen “room” description, the
fifth is the table inside description, and the sixth is table’s own “room”
listing.
The glass bottle is a transparent openable. This means we
can see the contents of the bottle even when it is closed.
bottle: containment
istransparent = true
location
= table
sdesc =
"glass bottle"
noun =
'bottle'
adjective
= 'glass'
;
>put apple in bottle
Done.
>close bottle
Closed.
>x bottle
The glass bottle is closed. In the glass bottle you
see an apple.
>take apple
You'll have to open the glass bottle first.
>open bottle
Opened.
>take apple
Taken.
The permeable resembles the transparentItem in many ways,
but they have some very significant differences: A permeable always allows
audible, olfactory, and reachable access. (For transparentItems these
particular forms of access are dependent upon the object’s isopen attribute.) Permeables can be used as cages,
ventilators, or even as high shelves. The following defines a cage that an
actor can throw objects outside of, but cannot retrieve them until he has
opened the cage.
cage: permeable
location
= greatHall
noun =
'cage'
isenterable = true
isopen = nil
sdesc =
"cage"
insideDesc = "The inside of the cage is cramped."
;
Notice that objects thrown from inside the cage will default
to landing inside the cage. This probably ought to be modified so that they fall
into the greatHall. Note, too, that once the cage is open access to the outside
that requires “touch” is available and the actor can take things. Such a cage
provides an interesting paradox, however, since <<bob, throw me the
keys>> will work, where <<bob, give me the keys>> will not.
The last two objects in the house come
out of the Inform Designers Manual, chapter IV: The Model World, section 14:
Things to enter, travel in and push around.
If the player gets into a
container and then closes it, the effect is like being in a different location.
(Unless the container has the transparent attribute and is therefore
see-through.) The interior may be dark, but if there's light to see by, the
player will want to see some kind of room description. In any case, many
enterable objects ought to look different from inside or on top. Inside a
vehicle, a player might be able to see a steering wheel and a dashboard, for
instance. On top of a cupboard, it might be possible to see through a skylight
window.
For this purpose, any
enterable object can provide an inside_description, which can be a string or a
routine to print one, as usual. If the exterior location is still visible, then
the "inside description'' is added to the normal room description, and
otherwise it becomes that description. As an extreme example, suppose that the
player gets into a huge cupboard, closes the door behind her and then gets into
a plastic cabinet inside that. The resulting room description might read like
so:
The huge cupboard (in the
plastic cabinet)
It's a snug little
cupboard in here, almost a room in itself.
In the huge cupboard you
can see a pile of clothes.
The plastic walls of the
cabinet distort the view.
The second line is the inside_description for the huge cupboard, and the fourth is that for the plastic cabinet.
Our hallway is a little Spartan, so we’ll add the cupboard
and cabinet objects to it.
hugeCupboard: containment
islockable = true
isenterable = true
location
= hallway
noun =
'cupboard'
adjective
= 'huge'
sdesc =
"huge cupboard"
insideDesc = "It's a snug little cupboard in here,
almost a room in itself."
;
plasticCabinet: containment
isopenable = true
istransparent = true
isenterable
= true
location
= hugeCupboard
noun =
'cabinet'
adjective
= 'plastic'
sdesc =
"plastic cabinet"
insideDesc = "The plastic walls of the cabinet distort the
view."
;
>get in cupboard
Okay, you're now in the huge cupboard.
Once we’re inside the cupboard we can take a look around.
Assuming its open the view is not unlike that of an enterable container or
enterable surface.
Hallway, in the huge cupboard
This
broad, dimly-lit corridor runs north and south.
It's a
snug little cupboard in here, almost a room in itself.
In the huge cupboard you can see a plastic cabinet and a pile of clothes.
Closing the cupboard is a mistake, unless we have a
lightsource!
>close cupboard
Closed.
>l
It's pitch black.
Our status line reads “In the dark.” And most actions are
unavailable to us. However, you can always open the cupboard and retrieve the
candle. At that point the view changes quite a bit.
Huge cupboard
It's a
snug little cupboard in here, almost a room in itself.
You see
a plastic cabinet and a pile of clothes here.
The “It’s a snug little cupboard…” line
is the inside description for the cupboard. Now we’re ready to get in the
plastic cabinet and take a look. But first we’ll put the candle in the cabinet,
to give us some perspective.
Huge cupboard, in the plastic cabinet
It's a
snug little cupboard in here, almost a room in itself.
You see
a pile of clothes here.
The
plastic walls of the cabinet distort the view.
In the plastic cabinet you
can see a candle (providing light).
And finally we close the cabinet, which is transparent.
Notice that the display changes subtly from that above.
Huge cupboard, in the plastic cabinet
It's a
snug little cupboard in here, almost a room in itself.
In the
huge cupboard you can see a pile of clothes.
The plastic walls of the cabinet distort the view.
You see
a candle (providing light) here.
Notice that the cupboard defines an islockable
attribute, which is probably sensible for a hall cupboard.
The islockable attribute
indicates whether this openable object should behave like a lockable
object. Set this attribute to true if you want the object to behave like a
lockable; otherwise leave it nil (default).
This object behaves like an
openable that can be locked and unlocked.
If enterable, it behaves
like an openable.
If the object defines a mykey attribute
then a key is required; otherwise no key is required to lock and unlock the object.
We can lock and unlock the cupboard without a key:
>lock cupboard
You'll have to close the huge cupboard first.
>close it
Closed.
>lock it
Locked.
>open it
It's locked.
>get in cupboard
You'll have to open the huge cupboard first!
>unlock it
Unlocked.
>open it
Opening the huge cupboard reveals a plastic cabinet
and a pile of clothes.
Now we’re ready to move outside of the house and demonstrate
the containment class isvehicle attribute.
First, we code the front door. This is not a containment
class object, but a door class object, which simplifies the coding and
eliminates the need to code two separate doorways. Notice that our frontDoor
is found in both the startroom and the frontYard. This second location is an
extension to the workbench sample.
frontDoor: door
foundin =
[ startroom, frontYard ]
noun =
'door'
adjective
= 'front'
sdesc =
"front door"
;
Our frontYard is simply a containment “room” with the lightsOn attribute set to true.
frontYard: containment
lightsOn
= true
toRiver =
bendInRiver
sdesc =
"Front Yard"
ldesc =
"%You're% standing in a broad front yard. The front door of
house
is to the north. "
north =
frontDoor
;
A vehicle is simply a “room” within a room that is moved
from one location to another. The defining features of a vehicle are certain
restrictions on actions when inside the vehicle, and the lack of a “room”
description when boarding.
In addition, a vehicle can have the behaviours of a surface
or container if it defines any of the issurface, isqsurface, iscontainer,
isqcontainer, isopenable, islockable, or istransparent attributes.
Our raft comes out of the TADS Author’s Manual, chapter 7:
Advanced Techniques; Nested Room Vehicles:
raft: containment
isvehicle
= true
location
= frontYard
sdesc =
"inflatable rubber raft"
adesc =
"an inflatable rubber raft"
noun =
'raft'
adjective
= 'inflatable' 'rubber'
isinflated = nil
ldesc =
"It's an inflatable rubber raft.
Currently,
it's
<<self.isinflated ? "inflated" : "not
inflated">>. "
verDoTake( actor ) =
{
if (
self.isinflated )
"You'll have to deflate it first. ";
else
pass doTake;
}
verDoInflateWith(
actor, iobj ) =
{
if (
self.isinflated) "It's already inflated! ";
}
doInflateWith( actor, iobj ) =
{
if (
self.isIn( actor ) )
"You'll have to drop it first. ";
else
{
"With some work, you manage to inflate
the raft. ";
self.isinflated = true;
}
}
verDoDeflate( actor ) =
{
if (
!self.isinflated )
"It's as deflated as it can get. ";
}
doDeflate( actor ) =
{
"You let the air out, and the raft collapses to a
compact pile of rubber. ";
self.isinflated = nil;
}
doBoard(
actor ) =
{
if (
self.isinflated )
pass doBoard;
else
"You'll have to inflate it first. ";
}
doUnboard( actor ) =
{
if (
isclass( self.location, riverRoom ) )
"Please keep your hands and arms inside the raft
at all times while the raft is in motion. ";
else
pass doUnboard;
}
out =
{
if (
isclass( self.location, riverRoom ) )
{
"You can't get out until you've landed the raft. ";
return( nil );
}
else
pass out;
}
verDoLaunch(
actor ) = {}
doLaunch(
actor ) =
{
if (
isclass( self.location, riverRoom ) )
"You're already afloat, if you didn't notice. ";
else
if ( self.location.toRiver == nil )
"There doesn't appear to be a suitable waterway here. ";
else
if ( parserGetMe().location != self )
"You'll have to get in the raft first. ";
else
{
"The raft drifts gently out into the river. ";
notify(
self, &moveDaemon, 0 );
self.counter = 0;
self.moveInto( self.location.toRiver );
}
}
verDoLand( actor ) = {}
doLand(
actor ) =
{
if
(not isclass( self.location, riverRoom
) )
"You're already fully landed. ";
else
if ( self.location.toLand == nil )
"There's no suitable landing here. ";
else
{
"You steer the raft up onto the shore. ";
unnotify( self, &moveDaemon );
self.moveInto( self.location.toLand );
}
}
moveDaemon =
{
"\bThe raft continues to float downstream. ";
self.counter++;
if (
self.counter > 1 )
{
self.counter = 0;
if
( self.location.downRiver == nil )
{
"The raft comes to the end of the river, and lands.";
self.moveInto( self.location.toLand );
unnotify( self, &moveDaemon );
}
else
{
self.moveInto( self.location.downRiver );
self.location.riverDesc;
}
}
}
;
Next we add all the other objects and grammar required to
make our sojourn down river a pleasant one.
inflateVerb: deepverb
sdesc =
"inflate"
verb =
'inflate' 'blow up'
ioAction(
withPrep ) = 'InflateWith'
prepDefault = withPrep
;
deflateVerb: deepverb
sdesc =
"deflate"
verb =
'deflate'
doAction
= 'Deflate'
;
pump: item
sdesc =
"pump"
location
= frontYard
noun =
'pump'
verIoInflateWith( actor ) = {}
ioInflateWith( actor, dobj ) =
{
dobj.doInflateWith( actor, self );
}
;
launchVerb: deepverb
sdesc =
"launch"
verb =
'launch'
doAction
= 'Launch'
;
landVerb: deepverb
sdesc =
"land"
verb =
'land'
doAction
= 'Land'
;
According to the Manual:
Note that
we'll expect each river room to have a property, riverDesc,
which displays
a message when the raft drifts into that room.
The
moveDaemon method will keep the raft in each river room for two turns,
then move
the raft to the next river room, calling riverDesc to note the entry.
When the
raft comes to the end of the river, the method will automatically land
the raft;
this means that the last river room must have a non-nil toLand property.
You could
alternatively put in a waterfall or other special effect when reaching
the end of
the river.
To build a
river, all you have to do is define a series of rooms of class riverRoom,
and point
the downRiver property in each to the next room downriver. Landings are built
by setting the toRiver and toLand properties of the landing room and
corresponding river room, respectively, to point to each other.
For this we devise a riverRoom class, which is a containment class
with a few extra details.
class riverRoom: containment
lightsOn
= true
riverDesc
= ""
toRiver =
nil
toLand =
nil
downRiver
= nil
ldesc = {
self.riverDesc; }
;
bendInRiver: riverRoom
sdesc =
"Bend In River"
ldesc =
"The river winds its way gently past your house."
riverDesc
= "The current is slow moving and serene at this spot along
the
river."
toLand =
frontYard
downRiver
= rapids
;
rapids: riverRoom
sdesc =
"Rapids"
ldesc =
"The waters rushing through the narrow canyons drown out all
other
sounds."
riverDesc
= "The water suddenly gets very choppy and dangerous
here."
downRiver
= broadRiver
;
broadRiver: riverRoom
sdesc =
"Broad River"
ldesc =
"This could easily be a lake, the water is as calm as a mill-
pond."
riverDesc
= "The river slows as it broadens out at this point."
toLand =
embankment
;
embankment: containment
lightsOn
= true
sdesc =
"Embankment"
ldesc =
"You're standing on the embankment of a broad river."
;
One last thing needs mentioning concerning the boarding and unboarding
of enterable surfaces and containers. The containment class allows you to
traverse the containment lists of objects, so that you can get on, get off of,
get in, or get out of objects that are within other objects.
For example, suppose we are in the hallway and issue the
command to get into the cabinet. Since the cabinet is inside the cupboard the
actor first travels to the cupboard, and then to the cabinet.
>get in cabinet
(getting into the huge cupboard)
Okay, you're now in the plastic cabinet.
The reverse happens if we should wish to leave the cupboard while in the cabinet.
>get out of cupboard
(getting out of the plastic cabinet)
Okay, you're no longer in the huge cupboard.
In general, you can board and unboard containment class
objects in this fashion as long as the target objects are within the scope of
the actor. What this means is simply this. Suppose you have a table in the
hallway that the actor is sitting on. A room description of the hallway will
indicate that the cupboard seems to contain a cabinet. The player can enter
>board cabinet and the actor will travel to the cabinet. However, from the
vantage of the cabinet the table does not appear in the room display, and so
>board table results in a “You don’t see any table here.” Message. This has
been implemented as a “what you see is what you can get” style of scope, which
can be changed via the passToLocation() and passToContents() methods used by
sense.t
Sense 3.1 employs a simple
mechanism for generating object reactions. The system confines its scope to the
top-level location of the actor and not to any extended locations. The sequence
of object reactions is as follows:
Object Sequence |
PreAction (lvl == 1) |
PostAction (lvl == 2) |
EndCommand (lvl == 3) |
actor |
actorPreAction |
actorPostAction |
actorEndCommand |
loc |
roomPreAction |
roomPostAction |
roomEndCommand |
object |
scopePreAction |
scopePostAction |
scopeEndCommand |
iobj |
ioPreAction |
ioPostAction |
ioEndCommand |
dobj |
doPreAction |
doPostAction |
doEndCommand |
The loc is calculated as the scope
ceiling for the actor and is based on visibility rules. If the actor is in a
closed non-transparent container then the loc is that container. Similarly, the
scopeXXXXX reactions objects are determined for this location using visibility
rules. If an abort statement is issued at the preAction or postAction stage
then object reactions for the following stages are bypassed, even through
postAction and endCommand are still executed by the parser.
Processing continues along this
sequence unless one of the methods issues an exitobj, exit, or abort statement.
No parameters are passed to these
methods, but they have complete access to the command object, including the
status of the command during the postAction and endCommand stages.
Access Rules
access_audible(o)
Purpose: An object meets
the requirements for crossing the contents threshold for audibility if it meets
the following conditions: (o.contentsAudible).
Calls: o.contentsAudible
Return true;
nil;
access_olfactory(o)
Purpose: An object meets
the requirements for crossing the contents threshold for “smellability” if it
meets the following conditions: (o.contentsOlfactory).
Calls: o.contentsOlfactory
Return: true;
nil;
access_qAudible(o)
Purpose: An object meets
the requirements for crossing the contents threshold for qAudibility if it
meets the following conditions: (access_audible(o) && !o.isqAudible).
Calls: access_audible()
o.isqAudible
Return: true;
nil;
access_qOlfactory(o)
Purpose: An object meets
the requirements for crossing the contents threshold for “quiet smellability”
if it meets the following conditions: (access_olfactory(o) &&
!o.isqOlfactory).
Calls: access_olfactory()
o.isqOlfactory
Return: true;
nil;
access_qVisible(o)
Purpose: An object meets
the requirements for crossing the contents threshold for qVisibility if it
meets the following conditions: (access_visible(o) && !o.isqVisible)
Calls: access_visible()
o.isqVisible
Return: true;
nil;
access_reachable(o)
Purpose: An object meets
the requirements for crossing the contents threshold for reachability if it
meets the following conditions: (o.contentsReachable).
Calls: o.contentsReachable
Return: true;
nil;
access_touchable(o)
Purpose: An object meets
the requirements for crossing the contents threshold for touchability if it
meets the following conditions: (o.contentsTouchable).
Calls: o.contentsTouchable
Return: true;
nil;
access_visible(o)
Purpose: An object meets
the requirements for crossing the contents threshold for visibility if it meets
the following conditions: (o.contentsVisible).
Calls: o.contentsVisible
Return true;
nil;
Path Building And General Path
Information Functions
buildClist(o)
Purpose: Build the clist (containment list) for
the object.
Calls: none
Return: clist;
path(vantage, target)
Purpose: Build the plist (path list) between the
vantage and target.
Calls: buildClist()
Return: plist format: [
lcc, path_list ]
getPlist(plist, flag)
Purpose: Returns the object or list corresponding to the plist and PTH_XXXX constant passed.
Calls: recursive
Return: lcc;
path;
vantage;
target;
tloc;
nil;
inPlist(plist, o, flag)
Purpose: Determines if o is found in the
specified part of the plist.
Calls: getPlist()
Return: true;
nil;
Path Validation Functions
blocksPath(plist, filter)
Purpose: Validate the path using the filter.
Special: sets target.accessfailed attribute [
filter, failloc, &passToXXXX ]
Return failloc;
nil;
checkAccessibility(a, v, d, p, I)
Purpose: Validate the command using accessibility rules of the model world.
Calls: testScope()
v.cantReach()
Return true;
nil;
proximity(vantage, target)
Purpose: Produces a flag indicating the
proximity of the target to the vantage and a list of all valid filters for the
path between the two.
Calls: path()
getPlist()
blocksPath()
Return [ prx_flag vsList ]
testScope(vantage, target, filter)
Purpose: Determine if the target is in the scope
of the vantage for the given filter. If no filter is passed then determines if
the object is sensible by at least one access rule.
Calls: path()
blocksPath()
Return true;
nil;
scope(vantage, filter)
Purpose: Return a list of objects that are in scope of the vantage
for the given filter.
Calls: testScope()
Return: scope_list;
loopOverScope(prop, vantage,
filter)
Purpose: Loops over every object in the scope of the vantage for the
given filter executing property.
Calls: testScope()
Return: none expected.
Disambiguation Methods
o.validActor(actor, prep, othobj)
Purpose: Used by the parser in disambiguating the actor element of a command. The default test is for reachability.
Calls: testScope()
Return true;
nil;
v.validXoList(actor, prep, othobj)
Purpose: Produce a list of valid objects to enhance the performance of the disambiguation process. Sense.t uses this method to store command elements.
Calls: none
Special: Stores command.actorPtr, command.prepPtr, and either command.iobjPtr or command.dobjPtr depending upon which method has been called.
Return nil; passing the original parsed noun phrase list to the validXo() methods.
v.validXo(actor, obj, seqno)
Purpose: Validate the direct or indirect object with respect to model world access rules / other rules during the parser disambiguation phase. Returns true if the object is sensible to the actor by any filter; otherwise calls checkRequires and returns nil.
Calls: testScope()
Special: Must set o.accessfailed attribute (done in blocksPath() in order for v.cantReach() to obtain the necessary information.)
Return true;
nil;
o.isVisible(vantage)
Purpose: Used by the parser disambiguation phase to determine how to report a disambiguation or accessibility error. By returning a default of true Sense bypasses the parser display “I don’t see any foo here.” This allows the error to be handled by the cantReach() mechanism.
Calls: none
Return true; (default)
Accessibility Failure Handling
Methods
v.cantReach(actor, dolist, iolist,
prep)
Purpose: Redirects the failed access to the appropriate cantXXXX
method.
Calls: cantSee()
cantHear()
cantSmell()
cantReach()
Uses: obj.accessfailed attribute
Return none expected.
o.cantSee(vantage, target)
Purpose: Displays a message for the failing location when access_visible access has failed for an object
Uses: command.cantReachWords
Return none expected
o.cantHear(vantage, target)
Purpose: Displays a message for the failing location when access_audible access has failed for an object
Calls: getAccessfailed()
testScope()
cantSee()
Return none expected
o.cantSmell(vantage, target)
Purpose: Displays a message for the failing location when access_olfactory access has failed for an object
Calls: getAccessfailed()
testScope()
cantSee()
Return none expected
o.cantReach(vantage, target)
Purpose: Displays a message for the failing location when access_reachable access has failed for an object
Calls: getAccessfailed()
testScope()
cantSee()
Return none expected
getAccessfailed(target, flag)
Purpose: Retrieves the specified element of the target.accessfailed attribute corresponding to one of the GAF_XXXX flags. The accessfailed attribute is a list set in blocksPath(). If no element is found the function returns nil.
Uses: target.accessfailed
Return access_rule;
failloc;
pass-to-method;
nil;
Room Display Methods
o.nrmLkAround(actor, verbosity)
Purpose: Displays the actor’s normal location description and contents for visible listable objects.
Calls: path()
blocksPath()
o.xtndLkAround()
Return none expected
o.xtndLKAround(actor, verbosity)
Purpose: Displays the actor’s extended location description and contents for visible listable objects.
Calls: none
Return none expected
o.nrmLnAround(actor, verbosity)
Purpose: Displays the actor’s normal location description and contents for audible listable objects.
Calls: path()
blocksPath()
o.xtndLkAround()
o.dispcont()
Uses: command.verbPtr
Return none expected
o.xtndLnAround(actor, verbosity)
Purpose: Displays the actor’s extended location description and contents for audible listable objects.
Calls: o.dispcont()
Return disptot;
o.nrmSmAround(actor, verbosity)
Purpose: Displays the actor’s normal location description and contents for olfactory listable objects.
Calls: path()
blocksPath()
o.xtndLkAround()
o.dispcont()
Return none expected
o.xtndSmAround(actor, verbosity)
Purpose: Displays the actor’s extended location description and contents for olfactory listable objects.
Calls: o.dispcont()
Return disptot;
o.dispcont(actor, filter,
listedAttr, descAttr)
Purpose: Displays the contents of the object using a given actor, filter, isListedXXXX attribute, and sense description.
Calls: bubbleScope()
path()
blocksPath()
Return disptot;
nil;
locale(actor, loc, access_rule)
Purpose: Displays the xtndXXAround of the loc using a given actor and access_rule. This display temporarily sets the location as isListedXXXX = true and isqXXXXX = nil; thus displaying the extended ldesc and contents.
Calls: loc.xtndXXAround()
Return no return expected
Extended Room Scope Methods
bubbleScope( vantage, limit, filter )
Purpose: Use
this function when you wish to obtain a list of all objects that are valid for
a given vantage and access_filter. The parameters are as follows:
vantage object whose scope is being determined.
limit scope
ceiling limit. The highest-level object
you wish the scope ceiling to be determined for; otherwise this should be nil.
filter Set
this to the access_filter that is the level of the scope for which you wish to
determine an object’s scope list.
Calls: scopeCeiling()
bubbleScopeList()
runRoutines()
Return: list of objects in scope within
top-level location
Examples:
Suppose
in our startroom we have a candle inside of a closed glass jar.
scope( candle, nil, &access_reachable )
Will
return the list:
[ glassJar candle ]
While
scope( candle, nil, &access_visible )
Will
return the list:
[ startroom glassJar candle theFloor Me ]
scopeCeiling( vantage, limit,
filter )
Purpose: Use
this function when you wish to obtain an object which is the scope ceiling for
the given vantage, limit, and access_filter. The parameters are the same as
those for scope().
Calls: none
Return: object that is scope ceiling
Examples:
Using
our example above, if we wanted to constrain our scope ceiling to the glassJar
we would set limit to the glassJar,
thus:
Scope( candle, glassJar,
&access_visible )
This
would return visibility limited to just those objects within the glassJar:
[ glassJar candle ]
Notice
that
Scope( glassJar, glassJar,
&access_visible )
Will
return
[ glassJar candle ]
This
is what we would expect. However, the following:
scope( startroom, glassJar,
&access_visible )
Would
return
[ startroom glassJar candle theFloor
Me ]
This
is because scopeCeiling begins its determination from startroom, working its
way up the object-tree. Since glassJar does not match the vantage or any
ancestor in the object-tree the limit is ignored and a valid ceiling is
determined for startroom using the access_filter as the only constraint. It is
important to bear this in mind when providing a limit to scopeCeiling() that
its primary function is to produce a valid scope ceiling for the vantage given
the scope-level and it will do so regardless of the value of limit.
If
the limit does not match the vantage or any ancestor in the object-tree the
limit is ignored and a valid ceiling is determined for the vantage using only
the access_filter as the constraint.
Using
our example, if we chose startroom as the scope ceiling limit and the glassJar
is closed then the following:
scope( candle, startroom,
&access_reachable )
Would
return
[ glassJar candle ]
Here
scope() begins with a scope ceiling of glassJar, not with startroom, because
the valid scope ceiling for the candle is further constrained by the
access_filter, which override the limit constraint.
bubbleScopeList( loc, vantage,
ceiling, filter )
Purpose: This
is a recursive function that builds and returns a list of all objects for a
given vantage, scope ceiling, and scope by working its way down the
object-tree. The parameters are as follows:
Loc
vantage object whose scope is being determined.
limit scope
ceiling limit. The highest-level object
you wish the scope ceiling to be determined for; otherwise this should be nil.
filter Set
this to the access_filter that is the level of scoping you wish to determine an
object’s scope list for.
Calls: recursive
Return: list of scopable objects, exclusive of top-level location
and floating items.
inBubbleScope( vantage, target, limit, filter )
Purpose: Use
this function to determine if the target is in the scope of the vantage for a
given limit and access_filter. If this is the case then the function returns
true; otherwise it returns nil. This function uses scope() in making its
determination, so objects such as floatingItem class objects will be taken into consideration. The
parameters are as follows:
vantage object whose scope is being evaluated.
target object being examined to determine if it is within the scope of the vantage.
limit scope
ceiling limit. The highest-level object
you wish the scope ceiling to be determined for; otherwise this should be nil.
filter set this
to the access_filter that is the level of scoping you wish to determine an
object’s scope list for.
Calls: bubbleScope()
Return true;
nil;
Sense makes modifications
to several of the functions and classes of ADV.T and STD.T to incorporate the
new scoping mechanisms. These modifications allow Sense to plug into the
standard ADV.T and STD.T libraries with minimal changes to the existing
mechanism. This is a brief summary of modifications to the standard behaviours
of ADV.T and STD.T.[1]
This function has
been modified to call parserGetMe().location.senseAround()
Modifies
verifyRemove() to 'bubble up' verGrab() check, but only as high as the
scope-ceiling. This allows us to grab things when we're inside closed
locations, such as enterables[2]
The method now
returns true for all cases. We want the verb.cantReach() function to handle the
case where the parser’s validXo list and the verification list are both empty.
This method has been
modified to call self.senseAround(actor, (!self.isseen) || global.verbose)
instead of self.lookAround(). We pass the actor as a parameter in order to
facilitate building a path between actor and other top-level locations
.thurough
This method is
modified to take the actor and sroot parameter. If sroot is true then we
display the status root; otherwise we suppress it. This is necessary for cases
when the method is called from room.senseAround(), when we don’t want to
display the status root from this method; or when it’s called directly, for the
command <<look>>, where we want the status root displayed.
This method is
modified to take the actor as a parameter. We use lookList to add the
parserGetMe() and remove the actor from the self.contents. This way we can display
the actorDesc based on the viewpoint of the actor, not the parserGetMe(). We
also loop through each tlist location here, calling xtndLkAround() for each
valid path.
This method returns
nil. This means that the entire parsed list is passed on to the validDo().
The validDo() method
should return true if the object is valid for the accessibility rule; otherwise
it should return nil. ADV.T uses reachability rules to determine the
accessibility of the object. Sense builds a path between the actor and the
object and then calls proximity(actor, object) to determine if there is at
least one valid access_filter for the path.
This method returns
nil. This means that the entire parsed list is passed on to the validIo().
The validIo() method
should return true if the object is valid for the accessibility rule; otherwise
it should return nil. ADV.T uses reachability rules to determine the
accessibility of the object. Sense builds a path between the actor and the
object and then calls proximity(actor, object) to determine if there is at
least one valid access_filter for the path.
This function has been
changed to use parserGetMe().location.senseAround().
This function has
been changed to use parserGetMe().location.senseAround().
Sense.t builds a command object, making the elements of the player command, plist, and noun phrase words available to the author.
The command object is defined as follows:
/*
* command: object
*
* The command object
carries all the pertinent information connected
* with the command,
including the plist and the word-string list that
* the user entered for the
direct and indirect objects of the command.
*/
command: object
actorPtr = nil
verbPtr = nil
dolistPtr = []
dobjPtr = nil
prepPtr = nil
iolistPtr = []
iobjPtr = nil
cantReachWords = []
disambigPhase = nil
;
The object is maintained by validXoList(), validXo(), preCommand(), verbAction(), and endCommand(). This is to ensure that the elements accurately reflect the player’s command.
The global.cantReachWords is the list of words corresponding
to the objects which cannot be accessed and invoke the parser call to
verb.cantReach().
The disambigPhase is set to true by preparseCmd() and set to
nil by preCommand(). The disambigObjType is a numeric value representing which
type of noun phrase is being evaluated at this point in the disambiguation
phase. The values are:
0: none
1: direct object
2: indirect object
These attributes can be used in conjunction with the
passToXXXX() methods, to determine whether the player command objects have been
disambiguated and to help constrain the nature of access limitation.
[1] This chapter does not document the changes to specific verbs. These changes involve changing the class of verbs to include dorequires and iorequires access rule pointers with which to validate the path list.
[2] The enterable class was introduced in the inform.t library extension. This is an openable / room class object that allows the actor to enter it and has now been superseded by the containment class.